/******************************************************************************
 * lowlevel.c
 * 
 * Interface functions to communicate with the user-safe device. Some of this
 * is inevitably device-dependent.
 * 
 * Copyright (c) 1999-2000, K A Fraser
 * 
 * $Id: lowlevel.c,v 1.1.1.1 2003/01/14 00:30:55 root Exp $
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include "trace.h"
#include "thread.h"
#include "defines.h"
/******************************************************************************
 * The acenic driver code contains the buffer ring definitions.
 */
#include <asm/page.h>
#include <asm/system.h>
#include <asm/cache.h>
#include <linux/types.h>
typedef unsigned long long u64;
typedef unsigned int       u32;
typedef unsigned short     u16;
typedef unsigned char      u8;
#include <acenic.h>
#include <acenic_usd.h>
#include <af_user.h>
#include <asm/bitops.h>

/******************************************************************************
 * Useful macros for manipulating circular list indices.
 */
#define INDEX_DIFF(p, c, s) (((p)-(c))&((s)-1))

#undef FALSE
#undef TRUE
typedef enum { FALSE, TRUE } bool;

#ifdef USE_PTH
extern pthread_mutex_t global_poll_mutex;
extern pthread_cond_t  global_poll_cond;
#else
extern int global_poll_mutex;
extern int global_poll_cond;
#endif 

/******************************************************************************
 * Structure containing the state for a USD connection.
 */
typedef struct 
{
    int             sock;
    usd_endpoint_t *shared_state;
    u32             user_to_phys_diff;
    usd2_ctrl_t    *acenic_ctrl;
    mbox_t         *acenic_mailbox;
    u32             usd_id;
    u16             tx_producer, old_tx_consumer;
    u16             rx_std_producer, rx_return_consumer;
    void           *transport_layer_state;
} usd_device_conn_t;

/******************************************************************************
 * Convert between a user-accessible physarea pointer and a
 * kernel-accessible physarea pointer.
 */
#define USER2PHYS(x) (((char *)(x)) + conn->user_to_phys_diff)
#define PHYS2USER(x) (((char *)(x)) - conn->user_to_phys_diff)
#define virt_to_phys(a)  ((unsigned long)(a) & ~0xc0000000)
#define phys_to_virt(a)  ((char *)((unsigned long)(a) | 0xc0000000))

/******************************************************************************
 * Mapping from connection identifiers to connection state structures.
 */
static usd_device_conn_t *conn_state[USD_CHANNELS] = {NULL};
static int num_conns = 0;

/******************************************************************************
 * usd_setup_device_connection:
 *   Create a connection from user space to the user-safe device, of type
 *   <sock_type>. Asynchronous callbacks are initialised for process <pid>.
 *
 *   Returns: pointer to device connection state, or NULL if error.
 */
usd_device_conn_t *usd_setup_device_connection(int sock_type, 
                                               pid_t pid,
                                               void *transport_layer_state)
{
    caddr_t shared_mem[2];
    usd_device_conn_t *conn;

    TRC("entered");
    
    /* Allocate space for connection state. */
    if ( (conn = malloc(sizeof(usd_device_conn_t))) == NULL )
    {
        ERR("leaving, could not allocate usd_device_conn structure");
        return(NULL);
    }
    memset(conn, 0, sizeof(usd_device_conn_t));

    /* Create the connection. */
    if ( (conn->sock = socket(AF_USER, sock_type, 0)) < 0 )
    {
	ERR("leaving, could not create socket");
        goto e1;
    }

    /* Register for asynchronous callbacks when there is work to do. */
    if ( ioctl(conn->sock, IOCTL_REGISTER_SOCKET_FOR_CALLBACKS, pid) == -1 )
    {
        ERR("leaving, could not register socket for callbacks");
        goto e2;
    }

    /* Get the shared data area. */
    if ( ioctl(conn->sock, IOCTL_GET_SHARED_DATA_AREA, shared_mem) == -1 )
    {
        ERR("leaving, could not get shared data area from card");
        goto e3;
    }

    TRC("phys buf=%p, (user ptr=%p)", shared_mem[0], shared_mem[1]);
    conn->shared_state      = (usd_endpoint_t *)shared_mem[1];
    conn->user_to_phys_diff = shared_mem[0] - shared_mem[1];

    /*
     * Squirrel away a pointer to the state kept by higher-level layers.
     * This allows us to process callbacks, and return pointers to 
     * higher-level structures, which other modules can use.
     */
    conn->transport_layer_state = transport_layer_state;

    num_conns++;

    return(conn);

e3: errno = ENOMEM;
e2: close(conn->sock);
e1: free(conn);
    return(NULL);
}

    
/******************************************************************************
 * usd_close_device_connection:
 *   Close a device connection which was opened by
 *   `usd_setup_device_connection'.
 *
 *   Returns: VOID.
 */
void usd_close_device_connection(usd_device_conn_t *conn)
{
    pthread_mutex_lock(&global_poll_mutex);
    /*
     * If we never made a connection then usd_id will be zero, which is a 
     * safe entry to clear as it will never be allocated to a USD channel.
     */
    conn_state[conn->usd_id] = NULL;
    close(conn->sock);
    free(conn);
    if ( --num_conns == 0 ) pthread_cond_broadcast(&global_poll_cond);
    pthread_mutex_unlock(&global_poll_mutex);
}


/******************************************************************************
 * usd_get_shared_data_area:
 *   For user-safe devices which use a single, pre-allocated and fixed area
 *   of memory for data transfer, this function returns a user-accessible
 *   pointer to this area in <start>
 *
 *   For devices which can pin down memory 'on the fly', the function
 *   `usd_lock_memory_for_sharing' should be used.
 *
 *   Returns: size of data area (in bytes),
 *            0 if the device pins down memory dynamically,
 *            -1 if there is an error.
 */
int usd_get_shared_data_area(usd_device_conn_t *conn, caddr_t *start)
{
    *start = (caddr_t)(conn->shared_state + 1);
    return(AF_USER_PHYSAREA_SIZE - sizeof(usd_endpoint_t));
}


/******************************************************************************
 * usd_bind_to_local_address:
 *   Binds connection <conn> to the local IP address <in_addr>, which may be
 *   wild-carded. Wild-carded fields will be filled in before returning.
 *   On exit, if <hw_addr> is non-NULL it will store the local MAC address.
 *
 *   Returns: 0 on success, -1 on failure.
 */
int usd_bind_to_local_address(usd_device_conn_t  *conn, 
                              struct sockaddr_in *in_addr, 
                              char               *hw_addr)
{
    int in_addr_len = sizeof(struct sockaddr_in);

    /* AF_USER does most of the work. */
    if ( bind(conn->sock, (struct sockaddr *)in_addr, in_addr_len) < 0 )
    {
	ERR("leaving, bind failed");
	return(-1);
    }

    if ( in_addr->sin_port == 0 || in_addr->sin_addr.s_addr == INADDR_ANY )
    {
        /* IP address was wildcarded. Get the values chosen by AF_USER. */
	if ( getsockname(conn->sock, 
                         (struct sockaddr *)in_addr, 
                         &in_addr_len) )
	{
	    ERR("leaving, getsockname failed");
	    return(-1);
	}
	if ( in_addr_len != sizeof(struct sockaddr_in) )
	{
	    ERR("leaving, getsockname length wrong");
	    return(-1);
	}
    }

    /* Finally, grab the local MAC address. */
    if ( hw_addr != NULL ) 
    {
        if ( ioctl(conn->sock, IOCTL_GET_LOCAL_MAC_ADDRESS, hw_addr) != 0 )
        {
            ERR("leaving, failed to get local MAC address");
            return(-1);
        }
    }

    return(0);
}


static void usd_init_new_channel(usd_device_conn_t *conn)
{
    /* Get the connection details... */
    conn->acenic_mailbox = (mbox_t *)ioctl(conn->sock, IOCTL_GET_MAILBOX, 0);
    conn->acenic_ctrl    = (usd2_ctrl_t *)ioctl(conn->sock, 
                                                IOCTL_GET_CONTROL_STRUCT, 0);
    conn->usd_id         = ioctl(conn->sock, IOCTL_GET_CONNECTION_ID, 0);
    TRC("mbox=%p, ctrl=%p, usd_id=%d",
        conn->acenic_mailbox, conn->acenic_ctrl, conn->usd_id);

    /* Add ourselves to the mapping table. */
    conn_state[conn->usd_id] = conn;
}


/******************************************************************************
 * usd_enable_connection_filtering:
 *   After a connection has been created to the NIC, this will enable TX
 *   and RX filtering. This should be called after downloading the first set
 *   of receive buffers to the card.
 * 
 *   Returns: 0 on success, -1 on failure.
 */
int usd_enable_connection_filtering(usd_device_conn_t *conn)
{
    if ( ioctl(conn->sock, IOCTL_INSTALL_FILTER, 0) )
    {
        ERR("leaving, failed to install filter");
        return(-1);
    }
    return(0);
}


/******************************************************************************
 * usd_connect_to_remote_address:
 *   Binds connection <conn> to the remote IP address <in_addr>.
 *
 *   Returns: 0 on success, -1 on failure.
 */
int usd_connect_to_remote_address(usd_device_conn_t        *conn,
                                  const struct sockaddr_in *in_addr)
{
    if ( connect(conn->sock, 
                 (struct sockaddr *)in_addr, 
                 sizeof(struct sockaddr_in)) < 0 )
    {
        ERR("leaving, connect failed");
        return(-1);
    }

    usd_init_new_channel(conn);

    return(0);
}


/******************************************************************************
 * usd_listen_for_incoming_connections:
 *   Set up connection <conn> to listen for incoming connection requests at
 *   its local IP address. Essentially a wrapper function for `listen', the
 *   <backlog> argument has the usual meaning.
 *
 *   Returns: 0 on success, -1 on failure.
 */
int usd_listen_for_incoming_connections(usd_device_conn_t *conn, int backlog)
{
    if ( listen(conn->sock, backlog) < 0 )
    {
        ERR("leaving, listen failed");
        return(-1);
    }

    usd_init_new_channel(conn);

    return(0);
}


/******************************************************************************
 * usd_completed_buffers_in_tx_queue:
 *   Returns number of completed buffers in <conn>'s tx queue.
 */
int usd_completed_buffers_in_tx_queue(usd_device_conn_t *conn)
{
    return(INDEX_DIFF(conn->acenic_ctrl->tx_cons,
                      conn->old_tx_consumer,
                      TX2_RING_ENTRIES));
}


/******************************************************************************
 * usd_space_in_tx_queue:
 *   Returns: number of free spaces in the transmit queue for connection
 *            <conn>.
 */
int usd_space_in_tx_queue(usd_device_conn_t *conn)
{
    return(TX2_RING_ENTRIES - 
           INDEX_DIFF(conn->tx_producer,
                      conn->old_tx_consumer,
                      TX2_RING_ENTRIES) - 1);
}


/******************************************************************************
 * usd_add_to_tx_queue:
 *   Adds the given raw data to the user-safe transmit queue for connection
 *   <conn>. Packets may be split into fragments, and the user-safe device
 *   will only be notified of new data to transmit when the final fragment
 *   is passed in to this function.
 *
 *   For efficiency, this function does not check for space in the queue.
 *   The function `usd_space_in_tx_queue' should be used for this purpose.
 *
 *   IMPORTANT: After queuing up all the fragments for a complete packet,
 *              usd_push_new_tx_bufs_to_nic() _MUST_ be called! Not only
 *              does this tell the NIC about the new packet, it sets the
 *              'final fragment' bit in the last buffer that was queued by
 *              this function.
 *
 *   Returns: VOID.
 */
#define TX2_DESC_END 0x00010000
void usd_add_to_tx_queue(usd_device_conn_t *conn,
                         void              *start, 
                         int                len)
{
    u32 txp = conn->tx_producer++ & (TX2_RING_ENTRIES - 1);

    conn->shared_state->tx2_ring[txp].buf = virt_to_phys(USER2PHYS(start)); 
    conn->shared_state->tx2_ring[txp].flagsize = len;
}


/******************************************************************************
 * usd_push_new_tx_bufs_to_nic:
 *   Pass the new tx producer index down to the NIC. This function _must_
 *   be called for every complete packet to be transmitted, as it sets the
 *   'final fragment' flag!!
 *
 *   Returns: VOID.
 */
void usd_push_new_tx_bufs_to_nic(usd_device_conn_t *conn)
{
    /* Set the 'final fragment' bit in the last queued buffer descriptor. */
    conn->shared_state->tx2_ring[(conn->tx_producer-1)&(TX2_RING_ENTRIES-1)].
        flagsize |= TX2_DESC_END;

    /* Kick the NIC. */
    conn->acenic_ctrl->tx_prod = conn->tx_producer;
    conn->acenic_mailbox->lo   = conn->usd_id;
} 
 

/******************************************************************************
 * usd_remove_from_tx_queue:
 *   Removes buffers from the transmit queue for connection <conn> that have
 *   been transmitted.
 *
 *   Returns: number of buffers removed from transmit queue.
 */
int usd_remove_from_tx_queue(usd_device_conn_t *conn)
{
    int num_removed;
    u32 txcons;

    /* We take a copy of the consumer index to avoid a race with the card. */
    txcons = conn->acenic_ctrl->tx_cons;
    num_removed = INDEX_DIFF(txcons, 
                             conn->old_tx_consumer, 
                             TX2_RING_ENTRIES);
    conn->old_tx_consumer = txcons;

    ASSERT(num_removed <= TX2_RING_ENTRIES);

    TRC("removed %d bds from queue", num_removed);
    return(num_removed);
}


/******************************************************************************
 * usd_tx_req_callback
 *   Set the tx reference index. A callback will occur when the card's
 *   producer overtakes this. The <ref> argument is the amount ahead of
 *   the producer that we want the ref to be set.
 */
void usd_tx_req_callback(usd_device_conn_t *conn, u32 ref)
{
    ioctl(conn->sock, IOCTL_TX_REQ_CALLBACK, ref + conn->old_tx_consumer);
}


/******************************************************************************
 * usd_completed_buffers_in_rx_queue:
 *   Returns number of completed (ie. filled) buffers in <conn>'s rx queue.
 */
int usd_completed_buffers_in_rx_queue(usd_device_conn_t *conn)
{
    return(INDEX_DIFF(conn->acenic_ctrl->rx_prod,
                      conn->rx_return_consumer,
                      RX2_RING_ENTRIES));
}


/******************************************************************************
 * usd_free_buffers_in_rx_queue:
 *   Returns number of free (waiting to be filled) bufs in <conn>'s rx
 *   queue.
 */
int usd_free_buffers_in_rx_queue(usd_device_conn_t *conn)
{
    return(INDEX_DIFF(conn->rx_std_producer,
                      conn->rx_return_consumer,
                      RX2_RING_ENTRIES));
}


/******************************************************************************
 * usd_rx_req_callback:
 *   Set the rx reference index. A callback will occur when the card's
 *   producer overtakes this. The <ref> argument is thye amount ahead of
 *   the producer that we want the ref to be set.
 */
void usd_rx_req_callback( usd_device_conn_t *conn, u32 ref )
{
    ioctl(conn->sock, IOCTL_RX_REQ_CALLBACK, ref + conn->rx_return_consumer);
}


/******************************************************************************
 * usd_add_to_rx_queue:
 *   Add an empty buffer, starting at <start> and of length <len>, to the
 *   end of the receive queue for connection <conn>.
 *
 *   For efficiency, this function does not check for space in the queue.
 *   The function `usd_space_in_rx_queue' should be used for this purpose.
 *
 *   Returns: VOID.
 */
void usd_add_hdr_to_rx_queue(usd_device_conn_t *conn, void *start, int len)
{
    u32 rxp = conn->rx_std_producer & (RX2_RING_ENTRIES - 1);

    conn->shared_state->rx2_ring[rxp].buf = 
      virt_to_phys(USER2PHYS(start)); 
    conn->shared_state->rx2_ring[rxp].flagsize = 
        len | RX2_DESCR_FLAGS_HDR;

    conn->rx_std_producer++;
}

void usd_add_data_to_rx_queue(usd_device_conn_t *conn, 
                              void *start, 
                              int len,
                              bool final_frag)
{
    u32 rxp = conn->rx_std_producer & (RX2_RING_ENTRIES - 1);

    conn->shared_state->rx2_ring[rxp].buf = 
      virt_to_phys(USER2PHYS(start)); 
    conn->shared_state->rx2_ring[rxp].flagsize = 
        len | (final_frag ? RX2_DESCR_FLAGS_EOP : RX2_DESCR_FLAGS_CONT);

    conn->rx_std_producer++;
}


/******************************************************************************
 * usd_push_new_rx_bufs_to_nic:
 *   Pass the new rx producer index down to the NIC.
 */
void usd_push_new_rx_bufs_to_nic(usd_device_conn_t *conn)
{
    conn->acenic_ctrl->free_prod = conn->rx_std_producer;
    ((conn->acenic_mailbox)-16)->lo = conn->usd_id;
}


/******************************************************************************
 * usd_remove_from_rx_queue:
 *   Removes a complete packet from the receive queue for connection <conn>.
 *
 *   Buffer management is rather simplistic at the moment. We therefore
 *   commit suicide if we ever get a buffer rejected by the NIC.
 *
 *   Apart from the above caveat, this function proceeds as follows:
 *   1. It checks if the next descriptor is 'DONE'. If not, return(-1).
 *   2. Fill in h_start/h_len with the details of the first buffer.
 *   3. Until an 'EOP' descriptor is reached, go on to the next descriptor,
 *      updating p_len appropriately. The very next buf after 'HDR' sets
 *      p_start. If there's only one descriptor for the entire packet,
 *      p_start is set to NULL.
 *
 *   Returns: 0 if successfully retrieved a packet, or -1 if no buffer.
 */
int usd_remove_from_rx_queue(usd_device_conn_t *conn,
                             void **h_start, int *h_len,
                             void **p_start, int *p_len)
{
    u32 rxp          = conn->rx_return_consumer & (RX2_RING_ENTRIES - 1);
    u16 orig_cons    = conn->rx_return_consumer;
    rx2_desc_t *desc = conn->shared_state->rx2_ring + rxp;
    void *p = NULL;
    int  pl = 0;

    /* Return immediately if there are no completed descriptors. */
    if ( !(desc->flagsize & RX2_DESCR_FLAGS_DONE) ) return(-1);
    if ( !(desc->flagsize & RX2_DESCR_FLAGS_OK) )   goto bad_buf;

    /*
     * We know that if we have one completed descriptor DMAed back to us,
     * we will have an entire packet's worth. Firt, check if there is a
     * special 'HDR' descriptor. If so, we fill in h_start/h_len.
     */
    if ( desc->flagsize & RX2_DESCR_FLAGS_HDR )
    {
        *h_start = PHYS2USER(phys_to_virt(desc->buf));
        *h_len   = desc->flagsize & 0xffff;
    }
    else
    {
        *h_start = NULL;
        *h_len   = 0;
        p  = PHYS2USER(phys_to_virt(desc->buf));
        pl = desc->flagsize & 0xffff;
    }

    /*
     * This loop takes the payload descriptors. There may be more than one
     * of these: although we know that payloads are contiguous in VM, each
     * page could be anywhere in physical memory. Hence the need for 
     * scatter/gather logic.
     */
    while ( !(desc->flagsize & RX2_DESCR_FLAGS_EOP) )
    {
        rxp  = ++conn->rx_return_consumer & (RX2_RING_ENTRIES - 1);
        desc = conn->shared_state->rx2_ring + rxp; 

        if ( !(desc->flagsize & RX2_DESCR_FLAGS_DONE) ) 
        {
            conn->rx_return_consumer = orig_cons;
            return(-1);
        }
        if ( !(desc->flagsize & RX2_DESCR_FLAGS_OK) ) goto bad_buf;

        if ( !p ) p  = PHYS2USER(phys_to_virt(desc->buf));
        pl += desc->flagsize & 0xffff;

    }

    /*
     * After the loop the consumer points at the last buf _processed_.
     * It should point to teh next one to be processed.
     */
    conn->rx_return_consumer++;

    TRC("Pkt OK: %lx %lx ret con = %lx, rx prd = %lx, fr prd = %lx\n",
        desc->buf, desc->flagsize, conn->rx_return_consumer,
        conn->acenic_ctrl->rx_prod, conn->acenic_ctrl->free_prod);
         
    *p_start = p;
    *p_len   = pl;
   
    return(0);

 bad_buf:
    ERR("Pkt was not OK: %lx %lx\n", desc->buf, desc->flagsize);
    *((int*)0)=0; // XXXKAF: need to think about this further.
    return(-1);
}


/******************************************************************************
 * wait_for_usd_conns_to_close:
 */
void wait_for_usd_conns_to_close(coid)
{
    pthread_mutex_lock(&global_poll_mutex);
    while ( num_conns > 0 ) 
    {
        pthread_cond_wait(&global_poll_cond, &global_poll_mutex);
    }
    pthread_mutex_unlock(&global_poll_mutex);
}


/******************************************************************************
 * CALLBACK FUNCTIONALITY:
 *   When the USD generates an event, we are signalled from the kernel.
 *   This is picked up by the thread library, which then calls a function
 *   which we specify. This function sets the relevant bits in an event map.
 */

static u32 usd_event_map[2 * (USD_CHANNELS+31)/32];
static void usd_callback_fn(unsigned int event)
{    
    set_bit(event&31, 
            usd_event_map + ((event&0xffff) >> 5) + 
            (event&RX_EVENT ? (USD_CHANNELS+31)/32 : 0));
}

/******************************************************************************
 * usd_init_callback:
 *   Sets the callback function, and the event bitmap.
 *   Returns the signal number that the stack should wait on for work, and
 *   the address of the event map which the stack can check.
 */
int usd_init_callback(void)
{
    memset(usd_event_map, 0, 2 * ((USD_CHANNELS+31)/32) * sizeof(u32));    
    pth_new_sighandler(usd_callback_fn);
    return(AF_USER_CALLBACK_SIGNAL);
}


/******************************************************************************
 * usd_get_next_connection_with_work:
 *   Scans the event table, looking for a connection with work to be done.
 *   Returns the identifier of the first connection it finds.
 *   NB. _doesn't_ clear event bits!
 *
 *   *work_to_do & 1 iff rx work to do for this conn
 *   *work_to_do & 2 iff tx work to do for this conn
 */
void *usd_get_next_connection_with_work(u32 *work_to_do)
{
    static u32 tx_work = 0;
    static u32 rx_work = 0;
    static int index   = 0;
    int        id;
    usd_device_conn_t *conn;
    u32 complete_scan = 0;

    for ( ; ; )
    {
        while ( !(rx_work || tx_work) )
        {
            if ( index++ == (USD_CHANNELS-1)/32 )
            {
                index = 0;
                if ( test_and_set_bit(0, &complete_scan) ) return(NULL);
            }

            /* Get a new bunch of connection event bits. */
            tx_work = usd_event_map[index];
            rx_work = usd_event_map[index + (USD_CHANNELS+31)/32];
        }

        if ( tx_work )
        {
            /* Next connection has tx work to do, and possibly rx work too. */
            int rwtd;
            __asm__ __volatile__(
                "bsfl %3,%0           # bsfl tx_work, id ; get first tx bit
                 btrl %0,%3           # btrl id, tx_work ; reset the tx bit
                 btrl %0,%2           # btrl id, rx_work ; reset the rx bit
                 sbbl %1,%1           # sbbl rwtd, rwtd  ; was the rx bit set?"
                : "=&r" (id), "=r" (rwtd), "=r" (rx_work), "=&r" (tx_work) 
                : "2" (rx_work), "3" (tx_work));
            *work_to_do = 2 - rwtd;   // rwtd == -1 iff rx bit was set above.
        }
        else
        {
            /* We have a connection with just receive work to do. */
            __asm__ __volatile__(
                "bsfl %1,%0
                 btrl %0,%1" : "=&r" (id), "=r" (rx_work) : "1" (rx_work));
            *work_to_do = 1;
        }

        if ( (conn = conn_state[id+(index<<5)]) != NULL )
        {
            return(conn->transport_layer_state);
        }
        else
        {
            clear_bit(id, usd_event_map + index);
            clear_bit(id, usd_event_map + index + (USD_CHANNELS+31)/32);
        }
    }
} 

void usd_clear_rx_event(usd_device_conn_t *usd_conn)
{
    u32 id = usd_conn->usd_id;
    clear_bit(id&31, usd_event_map + (id>>5) + (USD_CHANNELS+31)/32); 
}

void usd_set_rx_event(usd_device_conn_t *usd_conn)
{
    u32 id = usd_conn->usd_id;
    set_bit(id&31, usd_event_map + (id>>5) + (USD_CHANNELS+31)/32); 
}

void usd_clear_tx_event(usd_device_conn_t *usd_conn)
{
    u32 id = usd_conn->usd_id;
    clear_bit(id&31, usd_event_map + (id>>5)); 
}

void usd_set_tx_event(usd_device_conn_t *usd_conn)
{
    u32 id = usd_conn->usd_id;
    set_bit(id&31, usd_event_map + (id>>5)); 
}


/******************************************************************************
 * usd_dump_stats:
 *   Dump useful stats about all NIC connections. Intended to make debugging
 *   easier.
 */
void usd_dump_stats(void)
{
    int i;

    for ( i = 0; i < USD_CHANNELS; i++ )
    {
        usd_device_conn_t *conn = conn_state[i];
        if ( conn )
        {
            printf("Sock %d: txp=%04x, otxc=%04x, rxp=%04x, rxc=%04x\n",
                   conn->sock, conn->tx_producer, conn->old_tx_consumer, 
                   conn->rx_std_producer, conn->rx_return_consumer);
            printf("         txr=%04x, txp=%04x, rxr=%04x, rxfp=%04x, "
                   "rxp=%04x, txc=%04x\n",
                   conn->acenic_ctrl->tx_ref, conn->acenic_ctrl->tx_prod,
                   conn->acenic_ctrl->rx_ref, conn->acenic_ctrl->free_prod,
                   conn->acenic_ctrl->rx_prod, conn->acenic_ctrl->tx_cons);

        }
    }

    printf("Event bits(Tx,Rx): %08x %08x %08x %08x\n",
           usd_event_map[0], usd_event_map[1], 
           usd_event_map[2], usd_event_map[3]);
}
